package cn.sc.summer.file.util;

import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.ZipUtil;
import cn.sc.summer.constant.model.ZipParam;
import cn.sc.summer.exception.exceptions.CommonException;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileItemFactory;
import org.apache.commons.fileupload.disk.DiskFileItem;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.commons.CommonsMultipartFile;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;

/**
 * 类名：文件工具操作工具类
 *
 * @author a-xin
 * @date 2023/9/22 15:04
 */
public class FileUtil {

    /**
     * 构建ZIP文件并返回
     *
     * @param params   zip文件信息：key: 文件路径及文件名称，value: 需要写入的数据信息
     * @param response 返回数据载体
     */
    public static void createZipFile(Map<String, String> params, HttpServletResponse response, String fileName) {
        try {
            String[] paths = params.keySet().toArray(new String[0]);
            ByteArrayInputStream[] ins = params.values().stream().map(IoUtil::toUtf8Stream).toArray(ByteArrayInputStream[]::new);
            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
            ZipUtil.zip(outputStream, paths, ins);

            response.setHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=" +
                    URLEncoder.encode(fileName + ".zip", StandardCharsets.UTF_8.toString()));
            response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);
            IoUtil.write(response.getOutputStream(), false, outputStream.toByteArray());
        } catch (Exception e) {
            throw new CommonException("File exception: " + e.getMessage(), e);
        }
    }

    /**
     * 将流文件输出前端
     *
     * @param request      数据请求载体
     * @param response     数据返回载体
     * @param outputStream 输出流
     * @param mediaType    文件格式 {@link MediaType}
     * @param fileName     文件名称
     */
    public static void streamToResp(HttpServletRequest request, HttpServletResponse response, ByteArrayOutputStream outputStream, String mediaType, String fileName) throws IOException {
        fileName = FileUtil.getFileName(fileName, request);
        response.reset();
        response.setCharacterEncoding(StandardCharsets.UTF_8.toString());
        response.setHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename= " + fileName);
        String origin = request.getHeader(HttpHeaders.ORIGIN);
        response.addHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, origin);
        response.setContentType(mediaType);
        ServletOutputStream out = response.getOutputStream();
        out.write(outputStream.toByteArray());
    }

    /**
     * inputStream流转换成文件，如果文件不存在会自动创建文件
     *
     * @param inputStream 文件流
     * @param filePath    写入文件路径
     */
    public static void inputStreamToFile(InputStream inputStream, String filePath) {
        if (StrUtil.isBlank(filePath)) {
            throw new CommonException("The file path cannot be empty！");
        }
        try {
            File file = new File(filePath);
            if (file.exists()) {
                file.createNewFile();
            }
            OutputStream os = Files.newOutputStream(file.toPath());
            int read;
            byte[] bytes = new byte[1024 * 1024];
            //先读后写
            while ((read = inputStream.read(bytes)) > 0) {
                byte[] wBytes = new byte[read];
                System.arraycopy(bytes, 0, wBytes, 0, read);
                os.write(wBytes);
            }
            os.flush();
            os.close();
            inputStream.close();
        } catch (Exception e) {
            throw new CommonException("File exception: " + e.getMessage(), e);
        }
    }

    /**
     * MultipartFile转换为InputStream
     *
     * @param multipartFile web端文件流格式
     * @return 文件输入流
     */
    public static InputStream multipartToInputStream(MultipartFile multipartFile) throws IOException {
        InputStream inputStream = null;
        File file = null;
        try {
            // 创建临时文件
            file = File.createTempFile("temp", null);
            // 把multipartFile写入临时文件
            multipartFile.transferTo(file);
            inputStream = Files.newInputStream(file.toPath());
        } catch (IOException e) {
            throw new CommonException("File exception: " + e.getMessage(), e);
        } finally {
            file.deleteOnExit();
            inputStream.close();
        }
        return inputStream;
    }

    /**
     * 获取本地文件输入流
     *
     * @param filePath 文件路径
     * @return 文件输入流
     * @throws IOException 文件读取异常
     */
    public static InputStream readFileInputStream(String filePath) throws IOException {
        if (StrUtil.isBlank(filePath)) {
            throw new CommonException("The file path cannot be empty！");
        }
        File file = new File(filePath);
        if (file.exists()) {
            file.createNewFile();
        }
        return Files.newInputStream(file.toPath());
    }

    /**
     * 网络文件转换为byte二进制
     *
     * @param fileUrl 网络文件地址
     * @return byte二进制
     */
    public static byte[] toByteArray(String fileUrl) {
        try {
            URL url = new URL(fileUrl);
            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
            DataInputStream in = new DataInputStream(conn.getInputStream());
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            byte[] buffer = new byte[1024 * 4];
            int n;
            while ((n = in.read(buffer)) != -1) {
                out.write(buffer, 0, n);
            }
            return out.toByteArray();
        } catch (IOException e) {
            throw new CommonException("File exception: " + e.getMessage(), e);
        }

    }

    /**
     * 将网络文件保存本地文件
     *
     * @param fileUrl  网络文件地址
     * @param filePath 本地文件路径
     */
    public static void webFileToLocal(String fileUrl, String filePath) {
        if (StrUtil.isBlank(filePath)) {
            throw new CommonException("The file path cannot be empty！");
        }
        try {
            File file = new File(filePath);
            if (file.exists()) {
                file.createNewFile();
            }
            URL url = new URL(fileUrl);
            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
            DataInputStream in = new DataInputStream(conn.getInputStream());
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            byte[] buffer = new byte[1024 * 4];
            int n;
            while ((n = in.read(buffer)) != -1) {
                out.write(buffer, 0, n);
            }
            byte[] data = out.toByteArray();
            in.close();
            FileOutputStream fileOut = new FileOutputStream(filePath);
            fileOut.write(data);
            fileOut.close();
        } catch (IOException e) {
            throw new CommonException("File exception: " + e.getMessage(), e);
        }

    }

    /**
     * 直接获取网络文件的md5值
     *
     * @param fileUrl 网络文件地址
     * @return 网络文件md5值
     */
    public static String getWebFileMd5(String fileUrl) {
        try {
            URL url = new URL(fileUrl);
            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
            DataInputStream in = new DataInputStream(conn.getInputStream());
            return DigestUtils.md5Hex(in);
        } catch (IOException e) {
            throw new CommonException("File exception: " + e.getMessage(), e);
        }
    }

    /**
     * 获取网络文件的输入流
     *
     * @param fileUrl 网络文件地址
     * @return 文件流
     */
    public static InputStream getWebFileInputStream(String fileUrl) {
        DataInputStream in;
        try {
            URL url = new URL(fileUrl);
            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
            in = new DataInputStream(conn.getInputStream());
        } catch (IOException e) {
            throw new CommonException("File exception: " + e.getMessage(), e);
        }
        return in;
    }

    /**
     * 检查文件格式
     *
     * @param file 文件
     * @param type 需要校验的文件类型
     */
    public static void checkFileType(MultipartFile file, String type) {
        //判断文件是否存在
        if (null == file) {
            throw new CommonException("The file does not exist！");
        }
        //获得文件名
        String fileName = file.getOriginalFilename();
        if (!Objects.requireNonNull(StringUtils.lowerCase(fileName)).endsWith(type)) {
            throw new CommonException("The file format does not match！");
        }
    }

    /**
     * 根据浏览器，获取导出文件名称
     *
     * @param fileName 文件名称
     * @param request  请求体
     * @return 格式化后文件名
     */
    public static String getFileName(String fileName, HttpServletRequest request) {
        String userAgent = request.getHeader("USER-AGENT");
        try {
            //IE浏览器
            if (StringUtils.contains(userAgent, "MSIE")) {
                fileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8.toString());
                //google,火狐浏览器
            } else if (StringUtils.contains(userAgent, "Mozilla")) {
                fileName = new String(fileName.getBytes(), "ISO8859-1");
            } else {
                //其他浏览器
                fileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8.toString());
            }
        } catch (UnsupportedEncodingException e) {
            throw new CommonException("File transcoding exception: " + e.getMessage(), e);
        }
        return fileName;
    }

    /**
     * 将本地文件转换成流
     *
     * @param filePath 文件地址
     * @return 文件流
     */
    public static MultipartFile getMultipartFile(String filePath) {
        File file = new File(filePath);
        DiskFileItem item = new DiskFileItem("file"
                , MediaType.MULTIPART_FORM_DATA_VALUE
                , true
                , file.getName()
                , (int) file.length()
                , file.getParentFile());
        try {
            OutputStream os = item.getOutputStream();
            os.write(FileUtils.readFileToByteArray(file));
        } catch (IOException e) {
            e.printStackTrace();
        }
        return new CommonsMultipartFile(item);
    }

    /**
     * 提取zip文件
     *
     * @param multipartFile 文件
     * @return 文件集合
     * @throws IOException io异常
     */
    public static List<MultipartFile> unZip(MultipartFile multipartFile) throws IOException {
        checkFileType(multipartFile, ".zip");
        List<MultipartFile> list = new ArrayList<>();
        //获取文件输入流
        InputStream input = multipartFile.getInputStream();
        //获取ZIP输入流(一定要指定字符集Charset.forName("GBK")否则会报java.lang.IllegalArgumentException: MALFORMED)
        ZipInputStream zipInputStream = new ZipInputStream(new BufferedInputStream(input), Charset.forName("GBK"));
        ByteArrayOutputStream byteOut = null;
        InputStream inputStream = null;
        //定义ZipEntry置为null,避免由于重复调用zipInputStream.getNextEntry造成的不必要的问题
        ZipEntry ze;
        while ((ze = zipInputStream.getNextEntry()) != null) {
            byteOut = new ByteArrayOutputStream();
            IOUtils.copy(zipInputStream, byteOut);
            inputStream = new ByteArrayInputStream(byteOut.toByteArray());
            list.add(getMultipartFile(inputStream, ze.getName()));
        }
        //一定记得关闭流
        zipInputStream.closeEntry();
        input.close();
        if (byteOut != null) {
            byteOut.close();
        }
        if (inputStream != null) {
            inputStream.close();
        }
        return list;
    }

    /**
     * 获取封装得MultipartFile
     *
     * @param inputStream inputStream
     * @param fileName    fileName
     * @return MultipartFile
     */
    public static MultipartFile getMultipartFile(InputStream inputStream, String fileName) {
        FileItem fileItem = createFileItem(inputStream, fileName);
        //CommonsMultipartFile是feign对multipartFile的封装，但是要FileItem类对象
        return new CommonsMultipartFile(fileItem);
    }

    /**
     * FileItem类对象创建
     *
     * @param inputStream inputStream
     * @param fileName    fileName
     * @return FileItem
     */
    public static FileItem createFileItem(InputStream inputStream, String fileName) {
        FileItemFactory factory = new DiskFileItemFactory(16, null);
        String textFieldName = "file";
        FileItem item = factory.createItem(textFieldName, MediaType.MULTIPART_FORM_DATA_VALUE, true, fileName);
        int bytesRead = 0;
        byte[] buffer = new byte[8192];
        //使用输出流输出输入流的字节
        try (OutputStream os = item.getOutputStream()) {
            while ((bytesRead = inputStream.read(buffer, 0, 8192)) != -1) {
                os.write(buffer, 0, bytesRead);
            }
            inputStream.close();
        } catch (IOException e) {
            throw new IllegalArgumentException("文件上传失败");
        } finally {
            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (IOException ignored) {
                }
            }
        }
        return item;
    }

    /**
     * 将多个流转成zip文件输出
     *
     * @param zipList  文件流实体类对象
     * @param fileName zip包的名称
     * @param response 返回载体
     */
    public static void creatZip(List<ZipParam> zipList, String fileName, HttpServletRequest request, HttpServletResponse response) {
        BufferedInputStream bis = null;
        ZipOutputStream zos = null;
        OutputStream out = null;
        try {
            fileName = FileUtil.getFileName(fileName, request);
            out = response.getOutputStream();
            response.reset();
            response.setHeader("Content-Disposition",
                    "attachment;filename=" + fileName);
            response.setHeader("Access-Control-Allow-Origin", "*");
            response.setContentType("application/octet-stream; charset=utf-8");
            response.setCharacterEncoding("UTF-8");
            zos = new ZipOutputStream(out);
            byte[] bufs = new byte[1024 * 10];
            for (ZipParam zipParam : zipList) {
                String streamFileName = zipParam.getName();
                // 创建ZIP实体，并添加进压缩包
                ZipEntry zipEntry = new ZipEntry(streamFileName);
                zos.putNextEntry(zipEntry);
                // 读取待压缩的文件并写进压缩包里
                bis = new BufferedInputStream(zipParam.getInputstream(), 1024 * 10);
                int read;
                while ((read = bis.read(bufs, 0, 1024 * 10)) != -1) {
                    zos.write(bufs, 0, read);
                }
            }
            zos.close();
        } catch (IOException e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        } finally {
            // 关闭流
            try {
                if (null != bis) {
                    bis.close();
                }
                if (null != zos) {
                    zos.close();
                }
                if (null != out) {
                    out.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

}
